'''
Created on Jul 21, 2011

@author: Krystian Brachmanski
'''

import heapq

class MaxValueStatistics:
    """
    Class providing maximal value for multiple keys/series
    
    @author: Krystian Brachmanski
    """
    
    def __init__(self):
        """
        Default constructor clears the dictionary holding mappings key <-> max value    
        """
        self.stats = { }
    
    def addNewItem(self,name,value):
        """
        Check new name,value pair and store the new value for the specified key/name if it is greater than the one already
        available in the dictionary
        @param name: name of the item
        @param value: the value assigned to the item
        @return False if the value if there is already a higher value assigned to this item name or True in case the new value has been added to the dictionary      
        """
        if name in self.stats.keys():
            if value > self.stats[name]: 
                self.stats[name] = value
                return True
            else:
                return False
        else:
            self.stats[name] = value
            return True
        
    
    def getStatistics(self):
        """
        @return: statistics dictionary - form of multiple key <-> values    
        """
        return self.stats    

class ExecutionStatistics:
    """
    Class providing statistics about the number of occurences of a specific key/name. Used to count the number of processes created.
    
    @author: Krystian Brachmanski
    """
    
    def __init__(self):
        """
        Default constructor clears the dictionary holding the statistics    
        """
        self.execStats = { }
    
    def addNewItem(self,name):
        """
        Increase the execution/creation counter for the process name passed as parameter 
        @param name: the process name for which the execution shall be counted   
        """
        if name in self.execStats.keys():
            self.execStats[name] += 1
            return False
        else:
            self.execStats[name] = 1
            return True                
    
    def getStatistics(self):
        """
        @return: the statistics in the form of dictionary    
        """
        return self.execStats

class TopProcessList:
    '''
    Class responsible for providing the list of top n elements with the highest values 
    @author: Krystian Brachmanski    
    @requires: heapq
    '''
    
    def __init__(self,processNumber):
        '''
        Constructor
        @param processNumber: the length of the list (number of maximal elements to be stored/provided) 
        '''
        self.procNum = processNumber
        self.heap = []
        
    def getIndexInTheList(self, id,tuple_list):
        '''
        Provide the index of the element within the list of tuples
        @param id: the name of the element
        @param tuple_list: the list of tuples
        @return: the index within the list or -1 in case the element is not found  
        '''
        try:
            return [y[1] for y in tuple_list].index(id)
        except ValueError:
            return -1        
        
    def checkAndUpdateValue(self, value,heap,id,el=None):
        '''
        Check and update (if required the value on the heap)
        @param value: the value assigned to the process
        @param heap: the heap where to store the changed value
        @param el: lowest element in the least (optional, default value: None)  
        '''
        index = self.getIndexInTheList(id,heap)
        # Check if element already on the list if not append new one if yes updatd
        if (index < 0):
            return (value,id)
        else:
            el_tmp = heap.pop(index)
            # Is on the list - check if the value shall be updated (higher than the actual one for the process)
            if (value > el_tmp[0]):
                new = (value,id)
            else:
                new = (el_tmp[0],el_tmp[1])
            heapq.heappush(heap,new)
        return el
        
    def checkHeap(self,value,id):
        '''
        Check the parameters against the current list of top processes
        @param value: the value assigned to the process
        @param id: the name of the process 
        '''
        if len(self.heap) < int(self.procNum): 
            new = self.checkAndUpdateValue(value,self.heap,id)
            if (new != None):
                heapq.heappush(self.heap,(value,id))
        else:
            elem = heapq.heappop(self.heap)
            if (value > elem[0]): 
            # Check if the existing element is to be updated
                if (id == elem[1]):
                    elem = (value, elem[1])
                else:
                    elem = self.checkAndUpdateValue(value,self.heap,id,elem)
                    
            heapq.heappush(self.heap,elem)
        
    def getProcessList(self):
        '''
        @return: the top n processes list in the form of tuples        
        '''
        return self.heap


class BasicStatistics:
    """
    Class providing basic statistics: min, max, avg, q95, q99, q99.9, q99.99, q99.999 for input samples
    
    @author: Krystian Brachmanski    
    @requires: 
    """
            
    def __init__(self):
        """
        Default constructor - initializes min, max and samples list
        """    
        self.max = -1
        '''
        @ivar: stores the maximum value 
        '''
        self.min = 100000
        '''
        @ivar: stores the minimal value 
        '''
        self.results = [ ]
        '''
        @ivar: stores the list of the samples. Used to calculate the average and quantiles values 
        '''    
    
    def addSample(self,sample):
        """
        Adds a sample to the basic statistics module. It will be taken into account when calculating the min, max, avg and q95 value
        @param sample: Takes a single sample as an input
        """    
        if (sample < self.min):
            self.min = sample
        if (sample > self.max):
            self.max = sample
        self.results.append(sample) 
    
    @property
    def min(self):
        '''
        Return the minimum value from the samples
        '''    
        return self.min

    @property
    def max(self):
        """
        Return the maximum value from the samples
        """    
        return self.max
    
    def avg(self):
        """
        @return: the average value from the samples
        """    
        return float(sum(self.results)) / len(self.results)
        
    def q95(self):
        """
        @return: the quantile Q95 value from the samples
        """    
        pos = int(0.95*len(self.results))
        return self.results[pos]
    
    def q99(self):
        """
        @return: the quantile Q99 value from the samples
        """    
        pos = int(0.99*len(self.results))
        return self.results[pos]        

    def q999(self):
        """
        @return: the quantile Q99.9 value from the samples
        """    
        pos = int(0.999*len(self.results))
        return self.results[pos]        

    def q9999(self):
        """
        @return: the quantile Q99.99 value from the samples
        """    
        pos = int(0.9999*len(self.results))
        return self.results[pos]        

    def q99999(self):
        """
        @return: the quantile Q99.999 value from the samples
        """    
        pos = int(0.99999*len(self.results))
        return self.results[pos]        
    